﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.ComponentModel;

namespace MockupDesigner.AppItems.Commands
{
    public class DistributeCommand : ICommand
    {

        public CommandType CommandType
        {
            get { return CommandType.Undoable; }
        }

        private DesignerCanvas designerCanvas;
        private DistributeType distributeType;


        private IEnumerable<DesignerItem> selectedItems;
        private Dictionary<DesignerItem, double> elementsChanged = new Dictionary<DesignerItem, double>();

        public void Execute(object target, params object[] parameters)
        {
            if (!(target is DesignerCanvas))
            {
                throw new InvalidCastException("The target must be a DesignerCanvas");
            }
            this.designerCanvas = (DesignerCanvas)target;

            if (parameters[0] is string)
            {
                this.distributeType = (DistributeType)new EnumConverter(typeof(DistributeType)).ConvertFrom(parameters[0]);
            }
            else if (parameters[0] is DistributeType)
            {
                this.distributeType = (DistributeType)parameters[0];
            }
            else
            {
                throw new InvalidCastException("The target must be a DistributeType");
            }

            Redo();
        }

        public void Redo()
        {
            if (this.selectedItems == null)
            {
                this.selectedItems = GetSelectedItems();
            }
            if (this.selectedItems.Count() > 1)
            {
                double refA = Double.MaxValue;
                double refB = Double.MinValue;
                double sum = 0;
                foreach (DesignerItem item in this.selectedItems)
                {
                    refA = GetMinVal(refA, item);
                    refB = GetMaxVal(refB, item);
                    sum += GetSum(item);
                }

                double distance = Math.Max(0, (refB - refA - sum) / (this.selectedItems.Count() - 1));
                double offset = GetOffset();

                foreach (DesignerItem item in this.selectedItems)
                {
                    offset = ChangeVal(offset, distance, item);
                }
            }
        }

        private IEnumerable<DesignerItem> GetSelectedItems()
        {
            if (this.distributeType == DistributeType.Horizontal)
            {
                return from item in this.designerCanvas.SelectionService.CurrentSelection.OfType<DesignerItem>()
                       where item.ParentID == Guid.Empty
                       let itemLeft = Canvas.GetLeft(item)
                       orderby itemLeft
                       select item;
            }
            else
            {
                return from item in this.designerCanvas.SelectionService.CurrentSelection.OfType<DesignerItem>()
                       where item.ParentID == Guid.Empty
                       let itemLeft = Canvas.GetTop(item)
                       orderby itemLeft
                       select item;
            }
        }

        private double GetMinVal(double refA, DesignerItem item)
        {
            if (this.distributeType == DistributeType.Horizontal)
            {
                return Math.Min(refA, Canvas.GetLeft(item));
            }
            else
            {
                return Math.Min(refA, Canvas.GetTop(item));
            }
        }

        private double GetMaxVal(double refB, DesignerItem item)
        {
            if (this.distributeType == DistributeType.Horizontal)
            {
                return Math.Max(refB, Canvas.GetLeft(item) + item.ActualWidth);
            }
            else
            {
                return Math.Max(refB, Canvas.GetTop(item) + item.ActualHeight);
            }
        }

        private double GetSum(DesignerItem item)
        {
            if (this.distributeType == DistributeType.Horizontal)
            {
                return item.ActualWidth;
            }
            else
            {
                return item.ActualHeight;
            }
        }

        private double GetOffset()
        {
            if (this.distributeType == DistributeType.Horizontal)
            {
                return Canvas.GetLeft(this.selectedItems.First());
            }
            else
            {
                return Canvas.GetTop(this.selectedItems.First());
            }
        }

        private double ChangeVal(double offset, double distance, DesignerItem item)
        {
            if (this.distributeType == DistributeType.Horizontal)
            {
                double deltaH = offset - Canvas.GetLeft(item);
                foreach (DesignerItem di in this.designerCanvas.SelectionService.GetGroupMembers(item))
                {
                    double diLeft = Canvas.GetLeft(di);
                    if (!elementsChanged.ContainsKey(di))
                    {
                        elementsChanged.Add(di, diLeft);
                    }
                    Canvas.SetLeft(di, Canvas.GetLeft(di) + deltaH);
                }
                return offset + item.ActualWidth + distance;
            }
            else
            {
                double deltaV = offset - Canvas.GetTop(item);
                foreach (DesignerItem di in this.designerCanvas.SelectionService.GetGroupMembers(item))
                {
                    double diTop = Canvas.GetTop(di);
                    if (!this.elementsChanged.ContainsKey(di))
                    {
                        this.elementsChanged.Add(di, diTop);
                    }
                    Canvas.SetTop(di, Canvas.GetTop(di) + deltaV);
                }
                return offset + item.ActualHeight + distance;
            }
        }

        public void Undo()
        {
            if (this.distributeType == DistributeType.Horizontal)
            {
                foreach (DesignerItem item in this.elementsChanged.Keys)
                {
                    Canvas.SetLeft(item, this.elementsChanged[item]);
                }
            }
            else
            {
                foreach (DesignerItem item in this.elementsChanged.Keys)
                {
                    Canvas.SetTop(item, this.elementsChanged[item]);
                }
            }
        }

    }

    public enum DistributeType
    {
        Horizontal,
        Vertical
    }
}
